﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Linq.Expressions;

namespace Calculator
{
    class Stack
    {
        /// <summary>
        /// 計算用スタック
        /// </summary>
        private string[] stack = new string[100];
        /// <summary>
        /// 計算用スタックの個数
        /// </summary>
        private int num = 0;
        /// <summary>
        /// 計算式用スタック
        /// </summary>
        private string[] txtBxStack = new string[100];
        /// <summary>
        /// 計算式用スタックの個数
        /// </summary>
        private int txtBxNum = 0;

        /// <summary>
        /// 定数加算
        /// </summary>
        private const string PLUS = "+";
        /// <summary>
        /// 定数減算
        /// </summary>
        private const string MINUS = "-";
        /// <summary>
        /// 定数乗算
        /// </summary>
        private const string MULTI = "*";
        /// <summary>
        /// 定数除算
        /// </summary>
        private const string DIVID = "/";
        /// <summary>
        /// 定数エラーメッセージ
        /// </summary>
        private const string RTN_ERROR = "E";


        /// <summary>
        /// 計算用スタックへ値を積む
        /// </summary>
        /// <param name="x">積む値</param>
        public void Push(string inVal)
        {
            stack[num] = inVal;
            num++;
        }

        /// <summary>
        /// 計算用スタックから値を取り出す
        /// </summary>
        /// <returns>抜き出した値</returns>
        public string Pop()
        {
            string outVal = stack[num - 1];
            num--;

            return outVal;
        }


        /// <summary>
        /// 計算式用スタックへ積む
        /// </summary>
        /// <param name="x">積む値</param>
        public void TxtBxPush(string inTxtVal)
        {
            txtBxStack[txtBxNum] = inTxtVal;
            txtBxNum++;
        }

        /// <summary>
        /// 計算式用スタックから取り出す
        /// </summary>
        /// <returns>抜き出した値</returns>
        public string TxtBxPop()
        {
            string outTxtVal = txtBxStack[txtBxNum - 1];
            txtBxNum--;

            return outTxtVal;
        }


        /// <summary>
        /// スタックの中身が空
        /// </summary>
        /// <returns>スタックの個数が0か判断</returns>
        public bool NullStack()
        {
            return num == 0;
        }


        /// <summary>
        /// 計算用スタックの個数取得
        /// </summary>
        /// <returns>計算用スタックの個数</returns>
        public int GetNum()
        {
            return num;
        }

        /// <summary>
        /// 計算式用のスタックの個数取得
        /// </summary>
        /// <returns>計算式用のスタックの個数</returns>
        public int GetTxtNum()
        {
            return txtBxNum;
        }


        /// <summary>
        /// TxtBxCalculationへ計算式出力
        /// </summary>
        /// <returns>フォーマットとして値を返す</returns>
        public string TxtBxPrint(string cal)
        {
            const string TXTBXCAL = " {0}";
            const string TXTBXMINUSCAL = " ({0})";
            double changeDouble;
            string print = "";

            //計算式用のスタックを表示する
            for (int i = 0; i < txtBxNum; i++)
            {
                //数値の時
                if (double.TryParse(txtBxStack[i], out changeDouble))
                {

                    //値がマイナスなら
                    if (changeDouble < 0)
                    {
                        //TXTの形式に従う
                        print += string.Format(TXTBXMINUSCAL, txtBxStack[i]);
                    }
                    //値がプラスなら
                    else
                    {
                        //TXTBXCALの形式に従う
                        print += string.Format(TXTBXCAL, txtBxStack[i]);
                    }

                }
                //演算子の時
                else
                {
                    //TXTBXCALの形式に従う
                    print += string.Format(TXTBXCAL, txtBxStack[i]);
                }

            }

            //もし計算式の文字数が38文字を超えた場合、超える前の計算式を表示
            if (print.Length > 38)
            {
                print = cal;
            }

            return print;
        }


        /// <summary>
        /// 計算式スタックの初期化
        /// </summary>
        public void ClearStack()
        {
            //計算用スタックの初期化
            Array.Clear(stack, 0, num);
            num = 0;
            //計算式用のスタック初期化
            Array.Clear(txtBxStack, 0, txtBxNum);
            txtBxNum = 0;
        }

     



        /// <summary>
        /// クリアエンドを実行した時の処理
        /// </summary>
        public void ClearEndStack()
        {


            string popValue;
            string txtPopValue;
            double numValue = 0;


            //計算用スタックから取り出す
            popValue = Pop();
            //計算式用スタックから取り出す
            txtPopValue = TxtBxPop();

            if (txtBxNum == 0 && num == 0)
            {
                ClearStack();
                return;
            }



            //もし計算式用のスタックの一番上の値が乗算か除算の演算子であれば
            switch (txtBxStack[txtBxNum - 1])
            {
                //乗算の場合
                case MULTI:
                    // 計算用スタックから取り出した値を計算式用スタックから取り出した値で割る
                    numValue = double.Parse(popValue) / double.Parse(txtPopValue);
                    // 計算値を計算用のスタックへ積む
                    Push(numValue.ToString());
                    // *を積む
                    Push(MULTI);
                    break;

                //除算の場合
                case DIVID:
                    // 計算用スタックから取り出した値を計算式用スタックから取り出した値と掛ける
                    numValue = double.Parse(popValue) * double.Parse(txtPopValue);
                    // 計算値を計算用のスタックへ積む
                    Push(numValue.ToString());
                    Push(DIVID);
                    break;

            }

        }


        /// <summary>
        /// 後に押下された演算子を優先する
        /// </summary>
        /// <param name="oper">後に押された演算子</param>
        public void ChangeOper(string oper)
        {
            
            double changeDouble;

            //スタックの頂上の値が数値なら
            if (double.TryParse(stack[num-1], out changeDouble))
            {
                //押下された演算子を積む
                Push(oper);
                TxtBxPush(oper);
            }

            //スタックの頂上が数値でなければ
            else
            {
                //計算用スタックから値を一つ取り出す
                Pop();
                //後に押下された演算子を積む
                Push(oper);
                //計算式用スタックから値を一つ取り出す
                TxtBxPop();
                //後に押下された演算子を積む
                TxtBxPush(oper);
            }
        }



        /// <summary>
        /// 乗算と除算を先に行う
        /// </summary>
        /// <param name="inputValue">入力値</param>
        /// <param name="pushOperator">押下演算子</param>
        /// <returns></returns>
        public string Cast(double inputNumber, string pushOperator)
        {

            //スタックから取出した演算子を格納
            string popOperator;
            //スタックから取出した数値を格納
            string popNum;
            //計算結果を積む値を格納
            double value = 0;
            string rtnError = "";



            //スタックが空の状態
            if (NullStack()==true)
            {
                //入力値をスタックに積む
                Push(inputNumber.ToString());
                //押下された演算子を積む
                Push(pushOperator);

            }
            else
            {
                //スタックが空ではない状態
                //一つスタックから取出す
                popOperator = Pop();

                //取出した値が何か判別
                switch (popOperator)
                {

                    //取出した値が＋の場合
                    case PLUS:      // PLUSの計算
                    case MINUS:     // MINUSの計算
                        //取出した値を戻す
                        Push(popOperator);
                        //演算子を積む
                        Push(inputNumber.ToString());
                        //入力値を積む
                        Push(pushOperator);
                        break;

                    case MULTI:     // MULTIの計算
                        //もう一つ値を取出す
                        popNum = Pop();
                        //取出した値と入力値を掛ける
                        value = double.Parse(popNum) * inputNumber;
                        //スタックに積む
                        Push(value.ToString());
                        //プラスの演算子を積む
                        Push(pushOperator);
                        break;

                    case DIVID:     //DIVIDの計算
                        if (inputNumber == 0)
                        {
                            ClearStack();
                            rtnError = RTN_ERROR;
                            break;
                        }
                        else
                        {

                            //もう一つ値を取出す
                            popNum = Pop();
                            //取出した値と入力値を掛ける
                            value = double.Parse(popNum) / inputNumber;

                            //循環小数になる場合
                            if (Math.Floor(value) - value != 0)
                            {
                                //整数取得
                                int integer = (Int32)value;
                                //四捨五入場所取得
                                int roundoff = 10 - integer.ToString().Length;

                                //四捨五入結果代入
                                value = Math.Round(value, roundoff);
                            }

                            //スタックに積む
                            Push(value.ToString());
                            //プラスの演算子を積む
                            Push(pushOperator);
                            break;
                        }


                    //取り出した値が数値であれば、取り出した値を積む。
                    //演算子が押されるまで取り出される値が数値になる。
                    default:
                        Push(popOperator);
                        return rtnError;
                }
            }


            //計算式用スタックに値出力
            TxtBxPush(inputNumber.ToString());
            //計算式用スタックに演算子出力
            TxtBxPush(pushOperator);

            return rtnError;
        }


        /// <summary>
        /// スタックが0になるまで計算
        /// </summary>
        /// <param name="input">入力値</param>
        public string OutPut(double topInput)
        {
            //10進数表示の浮動小数点
            decimal result = 0;
            //スタックの頂上の演算子取得
            string popNum = Pop();
            //計算結果をstring型に格納する
            string resulttxt = "";
            //G29書式として設定
            const string SET_FORM = "G29";


            //各演算子の処理
            switch (popNum)
            {
                //＋ならばtopに入力値を格納
                case PLUS:
                    //入力値の値を確保
                    //＋をスタックへ積む
                    Push(PLUS);
                    break;


                //－ならばtopに‐を付けて入力値を格納
                case MINUS:
                    //入力値の値を確保
                    //－をスタックへ積む
                    Push(MINUS);
                    break;


                //＊ならばtopにポップして取得した値と入力値を掛けたものを格納
                case MULTI:
                    //スタックから取り出した数値と入力値を掛け合わせたものを確保
                    topInput = double.Parse(Pop()) * topInput;
                    break;

                //÷ならばtopにポップして取得した値と入力値を掛けたものを格納
                case DIVID:
                    //一番上に積まれていた値が0の場合
                    if (topInput == 0)
                    {
                        //エラーの文字列(E)を返す
                        resulttxt = RTN_ERROR;
                        return resulttxt;
                    }
                    //一番上に積まれていた値が0でない場合
                    else
                    {
                        //スタックから取り出した数値と入力値を掛け合わせたものを確保
                        topInput = double.Parse(Pop()) / topInput;

                        //循環小数になる場合
                        if (Math.Floor(topInput) - topInput != 0)
                        {
                            //整数取得
                            int integer = (Int32)topInput;
                            //四捨五入場所取得
                            int roundoff = 10 - integer.ToString().Length;

                            //四捨五入結果代入
                            topInput = Math.Round(topInput, roundoff);
                        }
                        break;
                    }

                //数値ならば頂上の値をその数値に置き換える
                default:
                    topInput = double.Parse(popNum);
                    break;

            }

 
            //得た結果を一番上にプッシュする
            Push(topInput.ToString());
            //スタックの一番底の値を取り出す
            double doubleResult = double.Parse(stack[0]);
            //decimal型に変換
            result = (decimal)doubleResult;

            //スタックの数を数える(リスト)
            for (int i = 1; i < num; i++)
            {
                //スタックの値を取得
                switch (stack[i])
                {
                    //スタックの値が＋の場合
                    case PLUS:
                        //resultの値と取得した演算子の次の値を足し合わせる
                        doubleResult = double.Parse(stack[i + 1]);
                        result = result + (decimal)doubleResult;
                        break;

                    //スタックの値が－の場合
                    case MINUS:
                        //resultの値と取得した演算子の次の値を引く
                        doubleResult = double.Parse(stack[i + 1]);
                        result = result - (decimal)doubleResult;
                        break;
                }
            }


            if (Math.Floor(result) - result != 0)
            {

                //整数部取得
                int integera = (Int32)result;

                //小数点部の長さ取得(10-x)
                int roundoff = 10 - integera.ToString().Length;

                //小数第x位を四捨五入
                result = Math.Round(result, roundoff);


                //string型へ変更
                resulttxt = result.ToString(SET_FORM);

            }
            else
            {
                //整数であればそのまま返り値に格納
                resulttxt = result.ToString();

                //もし出力が10以上になってしまうなら「E」を返す
                if (resulttxt.Length > 10)
                {
                    resulttxt = RTN_ERROR;
                }
            }

            //計算結果を返す
            return resulttxt;
        }

    }
}